03.02 - Fractales de Julia
Lenguajes Estructurados
Introducción a fractales de Julia
Los fractales son figuras geométricas que se repiten a diferentes escalas y exhiben autosimilaridad. Se pueden encontrar en la naturaleza, el arte y tienen aplicaciones en matemáticas, física y ciencias de la computación. Uno de los fractales más conocidos es el conjunto de Julia, que es un conjunto de números complejos que, al iterar a través de una función cuadrática, no tienden hacia infinito. El conjunto de Mandelbrot es un subconjunto del conjunto de Julia que utiliza una función cuadrática específica.
SDL (Simple DirectMedia Layer)
SDL es una biblioteca de gráficos en C que permite la representación de imágenes, el manejo de eventos y la interacción con dispositivos de entrada. Es multiplataforma y se utiliza comúnmente para el desarrollo de videojuegos y aplicaciones gráficas.
Generación de fractales de Julia utilizando punteros y SDL
Para generar un fractal de Julia, necesitamos iterar a través de cada píxel en una imagen y aplicar la función cuadrática:
$$Z(n+1) = Z(n)^2 + C$$
Aquí, Z(n) es un número complejo que representa la posición actual del píxel en el plano complejo, Z(n+1) es el resultado de la función cuadrática y C es un número complejo constante que determina la forma del fractal de Julia.
El algoritmo básico para generar un fractal de Julia es el siguiente:
- Para cada píxel en la imagen, convertir sus coordenadas a números complejos en un rango específico (por ejemplo, -2 < Re < 2 y -2 < Im < 2).
- Aplicar la función cuadrática de Julia a cada número complejo un número determinado de iteraciones o hasta que su magnitud supere un umbral (por ejemplo, 1000 iteraciones y umbral de 2).
- Colorear el píxel según el número de iteraciones que tomó para superar el umbral.
- Para utilizar SDL y punteros en este proceso, primero hace falta crear una ventana y una superficie para representar gráficamente el fractal.
Inclusión de cabeceras y declaración de función de utilidad
#include <SDL.h>
#include <complex.h>
#include <stdbool.h>
double map_range(double value, double in_min, double in_max, double out_min, double out_max) {
return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
Este bloque incluye las cabeceras necesarias para usar SDL, números complejos y tipos booleanos en el programa. La función map_range toma un valor en un rango dado (in_min, in_max) y lo mapea linealmente a otro rango (out_min, out_max). Se utiliza para convertir las coordenadas de píxeles en valores de números complejos en el espacio del fractal.
Inicialización de SDL y creación de la ventana
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("No se pudo inicializar SDL: %s", SDL_GetError());
return 1;
}
SDL_Window *window = SDL_CreateWindow("Fractal de Julia",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN);
if (!window) {
SDL_Log("No se pudo crear la ventana: %s", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_Surface *surface = SDL_GetWindowSurface(window);
Este bloque inicializa SDL, crea una ventana y obtiene una superficie asociada a la ventana. La superficie se utilizará para dibujar el fractal de Julia.
Generación del fractal de Julia
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double real = map_range(x, 0, width, -2.0, 2.0);
double imag = map_range(y, 0, height, -2.0, 2.0);
complex double z = real + imag * I;
complex double c = -0.8 + 0.156 * I;
int iterations = 0;
while (cabs(z) < 2 && iterations < max_iterations) {
z = z * z + c;
iterations++;
}
uint32_t color = SDL_MapRGB(surface->format, iterations % 256, 0, (iterations * 2) % 256);
uint32_t *pixels = (uint32_t *)surface->pixels;
pixels[y * width + x] = color;
}
}
El bloque anterior itera sobre todos los píxeles de la superficie, convierte sus coordenadas en números complejos y aplica el algoritmo del fractal de Julia. Los píxeles se colorean en función del número de iteraciones que tomaron para superar un umbral de magnitud.
Actualización de la ventana
SDL_UpdateWindowSurface(window);
Este bloque actualiza la ventana para que muestre la superficie con el fractal de Julia generado.
Bucle principal del programa
SDL_Event event;
bool running = true;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}
}
}
Este bloque contiene el bucle principal del programa que maneja los eventos, como el cierre de la ventana. Cuando se recibe el evento SDL_QUIT, se establece la variable running en false, lo que hace que el bucle termine y el programa finalice.
Limpieza y finalización
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
Este bloque limpia los recursos de SDL, destruye la ventana y finaliza el programa.
Compilación y ejecución
gcc -o julia julia.c -lSDL2
./julia
Este bloque compila el programa y lo ejecuta.
Apendice I : Números complejos en C
Los números complejos se pueden declarar y manipular utilizando el tipo de datos "complex", que se define en la biblioteca estándar "complex.h".
- Se pueden representar como dobles o como flotantes.
- Para representar números complejos como dobles, se utiliza la estructura complex double.
- Para representar números complejos como flotantes, se utiliza la estructura complex float.
La sintaxis para declarar un número complejo es la siguiente:
#include <complex.h>
complex double z;
En este ejemplo, se declara una variable "z" de tipo "complex double", que representa un número complejo con una parte real y una parte imaginaria de tipo "double".
Para asignar valores a las partes real e imaginaria de un número complejo, se pueden utilizar las funciones "creal" e "cimag", respectivamente.
La sintaxis para asignar valores a un número complejo es la siguiente:
#include <complex.h>
complex double z;
double a = 2.5;
double b = 1.5;
z = a + b * I;
En este ejemplo, se asigna el valor 2.5 a la parte real del número complejo "z" y el valor 1.5 a la parte imaginaria utilizando la constante "I" de la biblioteca "complex.h", que representa la unidad imaginaria (√(-1)) .
Una vez que se ha declarado y asignado valores a un número complejo, se pueden realizar operaciones aritméticas con él utilizando los operadores aritméticos convencionales (+, -, *, /) y las funciones de la biblioteca "complex.h", como "cabs" (para obtener el valor absoluto del número complejo) o "conj" (para obtener el conjugado del número complejo).
A continuación se muestran algunos ejemplos de operaciones aritméticas con números complejos en C:
#include <stdio.h>
#include <complex.h>
int main() {
complex double z1 = 2.0 + 3.0 * I;
complex double z2 = 1.0 - 1.0 * I;
printf("Suma: %f + %f i\n", creal(z1 + z2), cimag(z1 + z2));
printf("Resta: %f + %f i\n", creal(z1 - z2), cimag(z1 - z2));
printf("Producto: %f + %f i\n", creal(z1 * z2), cimag(z1 * z2));
printf("Cociente: %f + %f i\n", creal(z1 / z2), cimag(z1 / z2));
printf("Valor absoluto de z1: %f\n", cabs(z1));
printf("Conjugado de z1: %f + %f i\n", creal(conj(z1)), cimag(conj(z1)));
return 0;
}
Apendice II : SDL
SDL es una biblioteca de desarrollo de videojuegos y multimedia que se puede utilizar para crear aplicaciones gráficas. SDL se puede utilizar en C, C++ y otros lenguajes de programación.
SDL_Surface
SDL_Surface es una estructura que representa una superficie de dibujo. Una superficie de dibujo es una matriz de píxeles que se puede dibujar en una ventana. Una superficie de dibujo se puede utilizar para dibujar formas geométricas, imágenes y texto.
Coloreado del fractal de Julia
uint32_t *pixels = (uint32_t *)surface->pixels;
En esta línea, se declara un puntero a un entero sin signo de 32 bits llamado pixels. Luego, se asigna el puntero al arreglo de píxeles de la superficie (surface->pixels). El puntero se convierte explícitamente a (uint32_t *) porque surface->pixels es un puntero a void. Esto se hace para asegurar que el puntero apunte a un arreglo de enteros sin signo de 32 bits (donde cada entero representa un píxel en la superficie) en lugar de un puntero a un tipo de dato desconocido.
pixels[y * width + x] = color;
En esta línea, se establece el color del píxel en las coordenadas (x, y) en la superficie de la ventana.
Para hacer esto, se utiliza la fórmula y * width + x para calcular el índice lineal del píxel en el arreglo unidimensional de píxeles. La variable y
se multiplica por la anchura (width) para avanzar a la fila correspondiente en la superficie de la ventana y, luego, se suma la variable x para desplazarse a la columna correcta. Una vez que se obtiene el índice lineal, se asigna el valor color al píxel en esa posición en el arreglo pixels.
El valor de color es calculado en la línea anterior:
uint32_t color = SDL_MapRGB(surface->format, iterations % 256, 0, (iterations * 2) % 256);
Esta línea crea un valor de color de 32 bits utilizando la función SDL_MapRGB. La función toma como argumentos el formato de color de la superficie (surface->format) y los componentes de color rojo, verde y azul (RGB). En este caso, el componente rojo se calcula como iterations % 256, el componente verde se establece en 0, y el componente azul se calcula como (iterations * 2) % 256. Estos cálculos modulares aseguran que los valores de los componentes de color estén siempre en el rango [0, 255]. Los componentes de color se basan en el número de iteraciones realizadas en el bucle del fractal de Julia, lo que genera una representación visual única y colorida del fractal.
Después de establecer el color de cada píxel en la superficie de la ventana, se llama a la función
SDL_UpdateWindowSurface(window);
Esta función actualiza la ventana con los cambios realizados en la superficie. En otras palabras, hace que los píxeles coloreados sean visibles en la ventana de la aplicación.
Ejemplo de SDL en C
#include <SDL.h>
int main() {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
SDL_Log("No se pudo inicializar SDL: %s", SDL_GetError());
return 1;
}
SDL_Window *window = SDL_CreateWindow("Hola mundo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
if (!window) {
SDL_Log("No se pudo crear la ventana: %s", SDL_GetError());
SDL_Quit();
return 1;
}
SDL_Event event;
bool running = true;
while (running) {
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
running = false;
}
}
}
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Referencias
Python
La línea de código
plt.imshow(iter_count, cmap='viridis', extent=[xmin, xmax, ymin, ymax])
se utiliza para mostrar una imagen en la ventana del gráfico utilizando la función imshow de la biblioteca matplotlib.
Los argumentos que se le pasan a esta función son los siguientes:
- iter_count: Esta es la matriz que contiene el número de iteraciones que se han realizado para cada punto del plano complejo. Esta matriz se ha generado previamente en el código utilizando un bucle for y la función julia(z, c) que define el fractal de Julia.
cmap='viridis': Este es el mapa de colores que se utilizará para representar los diferentes valores en la matriz iter_count. En este caso, se utiliza el mapa de colores "viridis", que es un mapa de colores predefinido en matplotlib que va desde el color azul oscuro para los valores más pequeños, hasta el color amarillo claro para los valores más grandes.
extent=[xmin, xmax, ymin, ymax]: Este argumento se utiliza para definir los límites del eje x e y en el gráfico. Los valores xmin, xmax, ymin e ymax se han definido previamente en el código para definir los límites del plano complejo que se está dibujando.
En resumen, la línea de código plt.imshow(iter_count, cmap='viridis', extent=[xmin, xmax, ymin, ymax]) muestra la matriz iter_count en una imagen utilizando el mapa de colores "viridis" y los límites del eje x e y definidos por xmin, xmax, ymin e ymax.